/**
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import { url, util } from "@kit.ArkTS";
import nativeLib from 'liblibrary.so';
import { AssertionError } from "../Error/error";


export function StringToUint8Array(str: string): Uint8Array{
    let encoder = util.TextEncoder.create()
    return encoder.encodeInto(str)
}
/**
 * Returns this as a non-negative integer, or 0 if it is negative, or [Int.MAX_VALUE] if it is too
 * large, or [defaultValue] if it cannot be parsed.
 */
export function toNonNegativeInt(str: string, defaultValue: number): number {
    const value = parseInt(str, 10);
    if (isNaN(value) || value < 0) {
        return defaultValue;
    }
    return value;
}
/**
 * Returns the index of the next non-whitespace character in this. Result is undefined if input
 * contains newline characters.
 */
export function indexOfNonWhitespace(str: string, startIndex: number = 0): number {
    for (let i = startIndex; i < str.length; i++){
        let c = str[i]
        if (c != ' ' && c != '\t'){
            return i
        }
    }
    return str.length
}

/** Returns true if the [String] is ASCII encoded (0-127). */
export function isAscii(str: string) {
    if (str.length === 0) return true;
    const encoder = new util.TextEncoder(); // UTF-8 encoder
    const utf8Bytes = encoder.encodeInto(str);
    return str.length === utf8Bytes.length;
}


/**
 * Returns the index of the first character in this string that is either a control character (like
 * `\u0000` or `\n`) or a non-ASCII character. Returns -1 if this string has no such characters.
 */
export function indexOfControlOrNonAscii(str: string): number {
    for (let i = 0; i < str.length; i++){
        let c = str[i]
        if (c <= '\u001f' || c >= '\u007f') {
            return i
        }
    }
    return -1
}


/** Equivalent to `string.substring(startIndex, endIndex).trim()`. */
export function trimSubstring(str: string, startIndex: number = 0, endIndex: number = str.length): string {
    let start = indexOfFirstNonAsciiWhitespace(str,startIndex, endIndex)
    let end = indexOfLastNonAsciiWhitespace(str,start, endIndex)
    return str.slice(start, end)
}

/**
 * Decrements [endIndex] until `input[endIndex - 1]` is not ASCII whitespace. Stops at [startIndex].
 */
export function indexOfLastNonAsciiWhitespace(str: string, startIndex: number = 0, endIndex: number = str.length): number {
    // 参数边界处理
    startIndex = Math.max(0, startIndex);
    endIndex = Math.min(str.length, endIndex);

    if (startIndex >= endIndex) return startIndex;

    // ASCII 空白字符集合
    const isAsciiWhitespace = (char: string): boolean => {
        return char === '\t' || char === '\n' || char === '\f' || char === '\r' || char === ' ';
    };

    // 从 endIndex - 1 开始向前遍历
    for (let i = endIndex - 1; i >= startIndex; i--) {
        if (!isAsciiWhitespace(str[i])) {
            return i + 1; // 返回非空白字符的下一个位置
        }
    }

    // 全部是空白，返回起始位置
    return startIndex;
}

/**
 * Increments [startIndex] until this string is not ASCII whitespace. Stops at [endIndex].
 */
export function indexOfFirstNonAsciiWhitespace(str: string, startIndex: number = 0, endIndex: number = str.length): number {
    // 参数边界处理
    startIndex = Math.max(0, startIndex);
    endIndex = Math.min(str.length, endIndex);
    if (startIndex >= endIndex) return startIndex;
    // ASCII 空白字符集合
    const isAsciiWhitespace = (char: string): boolean => {
        return char === '\t' || char === '\n' || char === '\f' || char === '\r' || char === ' ';
    };
    for (let i = startIndex; i <= endIndex; i++) {
        if (!isAsciiWhitespace(str[i])) {
            return i; // 返回非空白字符的下一个位置
        }
    }
    return endIndex
}

/**
 * Returns the index of the first character in this string that is [delimiter]. Returns [endIndex]
 * if there is no such character.
 */
export function delimiterOffset(str: string,
    delimiter: string, // JS 字符用 string 表示
    startIndex: number = 0,
    endIndex: number = str.length): number {
    // 参数校验
    startIndex = Math.max(0, startIndex);
    endIndex = Math.min(str.length, endIndex);
    if (startIndex >= endIndex) return endIndex;
    const delimiterChar = delimiter.charAt(0);
    if (delimiterChar === '') return endIndex;
    for (let i = startIndex; i < endIndex; i++) {
        if (str[i] === delimiterChar) {
            return i;
        }
    }
    return endIndex
}

export function canParseAsIpAddress(host: string): boolean {
    const ipRegex = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/;
    const match = host.match(ipRegex);

    if (!match) return false;

    // 检查每个段是否在 0-255 范围内
    return match.slice(1).every(part => {
        const num = parseInt(part, 10);
        return num >= 0 && num <= 255;
    });
}

export function toHttpDateOrNull(value?: string): Date | null{
    if (value === undefined){
        return null;
    }
    // 使用RFC 1123解析: EEE, dd MMM yyyy HH:mm:ss 'GMT'
    value = value.trim();
    if (value.length === 0) return null;
    const timestamp = Date.parse(value);
    if (isNaN(timestamp)) return null;

    const date = new Date(timestamp);
    return date;
}

export function toCanonicalHost(pattern: string): string | null {
    const host = pattern;
    // If the input contains a :, it's an IPv6 address.
    if (host.includes(':')) {
        // If the input is encased in square braces "[...]", drop 'em.
        const address = (host.startsWith('[') && host.endsWith(']'))
            ? decodeIpv6(host, 1, host.length - 1)
            : decodeIpv6(host, 0, host.length);
        
        if (address === null) return null;
        
        if (address.length === 16) {
            return inet6AddressToAscii(address);
        }
        if (address.length === 4) {
            // An IPv4-mapped IPv6 address
            return `${address[0] & 0xff}.${address[1] & 0xff}.${address[2] & 0xff}.${address[3] & 0xff}`;
        }
        throw new AssertionError(`Invalid IPv6 address: '${host}'`);
    }
    
    // Try to convert to ASCII (IDN handling with Punycode)
    try {
        const result = nativeLib.idnToASCII(host).toLowerCase();
        if (result.length === 0) return null;
        
        // Confirm that the result doesn't contain any illegal characters
        if (containsInvalidHostnameAsciiCodes(result)) {
            return null;
        }
        return result;
    } catch (e) {
        return null;
    }
}

function containsInvalidHostnameAsciiCodes(str: string): boolean {
    for (let i = 0; i < str.length; i++) {
        const c = str.charAt(i);
        const code = str.charCodeAt(i);
        
        // The WHATWG Host parsing rules accepts some character codes which are invalid
        if (code <= 0x001f || code >= 0x007f) {
            return true;
        }
        // Check for the characters mentioned in the WHATWG Host parsing spec:
        // U+0000, U+0009, U+000A, U+000D, U+0020, "#", "%", "/", ":", "?", "@", "[", "\", and "]"
        if (' #%/:?@[\\]'.indexOf(c) !== -1) {
            return true;
        }
    }
    return false;
}

/** Decodes an IPv6 address like 1111:2222:3333:4444:5555:6666:7777:8888 or ::1. */
function decodeIpv6(input: string, pos: number, limit: number): number[] | null {
    const address: number[] = new Array(16).fill(0);
    let b = 0;
    let compress = -1;
    let groupOffset = -1;
    
    let i = pos;
    while (i < limit) {
        if (b === address.length) return null; // Too many groups.
        
        // Read a delimiter.
        if (i + 2 <= limit && input.substring(i, i + 2) === '::') {
            // Compression "::" delimiter
            if (compress !== -1) return null; // Multiple "::" delimiters.
            i += 2;
            b += 2;
            compress = b;
            if (i === limit) break;
        } else if (b !== 0) {
            // Group separator ":" delimiter.
            if (input.charAt(i) === ':') {
                i++;
            } else if (input.charAt(i) === '.') {
                // If we see a '.', rewind to the beginning of the previous group and parse as IPv4.
                if (!decodeIpv4Suffix(input, groupOffset, limit, address, b - 2)) return null;
                b += 2; // We rewound two bytes and then added four.
                break;
            } else {
                return null; // Wrong delimiter.
            }
        }
        
        // Read a group, one to four hex digits.
        let value = 0;
        groupOffset = i;
        while (i < limit) {
            const hexDigit = parseHexDigit(input.charAt(i));
            if (hexDigit === -1) break;
            value = (value << 4) + hexDigit;
            i++;
        }
        const groupLength = i - groupOffset;
        if (groupLength === 0 || groupLength > 4) return null; // Group is the wrong size.
        
        // We've successfully read a group. Assign its value to our byte array.
        address[b++] = (value >>> 8) & 0xff;
        address[b++] = value & 0xff;
    }
    
    // All done. If compression happened, we need to move bytes to the right place.
    if (b !== address.length) {
        if (compress === -1) return null; // Address didn't have compression or enough groups.
        // Move bytes after compression point to the end
        const bytesToMove = b - compress;
        for (let j = 0; j < bytesToMove; j++) {
            address[address.length - bytesToMove + j] = address[compress + j];
        }
        // Fill the gap with zeros
        for (let j = compress; j < compress + (address.length - b); j++) {
            address[j] = 0;
        }
    }
    
    return address;
}

/** Decodes an IPv4 address suffix of an IPv6 address, like 1111::5555:6666:192.168.0.1. */
function decodeIpv4Suffix(
    input: string,
    pos: number,
    limit: number,
    address: number[],
    addressOffset: number
): boolean {
    let b = addressOffset;
    
    let i = pos;
    while (i < limit) {
        if (b === address.length) return false; // Too many groups.
        
        // Read a delimiter.
        if (b !== addressOffset) {
            if (input.charAt(i) !== '.') return false; // Wrong delimiter.
            i++;
        }
        
        // Read 1 or more decimal digits for a value in 0..255.
        let value = 0;
        const groupOffset = i;
        while (i < limit) {
            const c = input.charAt(i);
            if (c < '0' || c > '9') break;
            if (value === 0 && groupOffset !== i) return false; // Reject unnecessary leading '0's.
            value = value * 10 + (c.charCodeAt(0) - '0'.charCodeAt(0));
            if (value > 255) return false; // Value out of range.
            i++;
        }
        const groupLength = i - groupOffset;
        if (groupLength === 0) return false; // No digits.
        
        // We've successfully read a byte.
        address[b++] = value;
    }
    
    // Check for too few groups. We wanted exactly four.
    return b === addressOffset + 4;
}

/** Encodes an IPv6 address in canonical form according to RFC 5952. */
function inet6AddressToAscii(address: number[]): string {
    // Go through the address looking for the longest run of 0s. Each group is 2-bytes.
    // A run must be longer than one group (section 4.2.2).
    // If there are multiple equal runs, the first one must be used (section 4.2.3).
    let longestRunOffset = -1;
    let longestRunLength = 0;
    
    let i = 0;
    while (i < address.length) {
        const currentRunOffset = i;
        while (i < 16 && address[i] === 0 && address[i + 1] === 0) {
            i += 2;
        }
        const currentRunLength = i - currentRunOffset;
        if (currentRunLength > longestRunLength && currentRunLength >= 4) {
            longestRunOffset = currentRunOffset;
            longestRunLength = currentRunLength;
        }
        i += 2;
    }
    
    // Emit each 2-byte group in hex, separated by ':'. The longest run of zeroes is "::".
    const result: string[] = [];
    i = 0;
    while (i < address.length) {
        if (i === longestRunOffset) {
            result.push(':');
            i += longestRunLength;
            if (i === 16) result.push(':');
        } else {
            if (i > 0) result.push(':');
            const group = ((address[i] & 0xff) << 8) | (address[i + 1] & 0xff);
            result.push(group.toString(16));
            i += 2;
        }
    }
    return result.join('');
}

function parseHexDigit(c: string): number {
    if (c >= '0' && c <= '9') return c.charCodeAt(0) - '0'.charCodeAt(0);
    if (c >= 'a' && c <= 'f') return c.charCodeAt(0) - 'a'.charCodeAt(0) + 10;
    if (c >= 'A' && c <= 'F') return c.charCodeAt(0) - 'A'.charCodeAt(0) + 10;
    return -1;
}

export function regionMatches(hostname: string, thisOffset: number, pattern: string, otherOffset: number, length: number): boolean{
    if (thisOffset < 0 || otherOffset < 0 || length < 0) return false;
    if (thisOffset + length > hostname.length || otherOffset + length > pattern.length) return false;
    // 从 hostname 提取子字符串
    const a = hostname.substring(thisOffset, thisOffset + length);
    // 从 pattern 提取子字符串
    const b = pattern.substring(otherOffset, otherOffset + length);
    return a === b;
}

export default {
    isEmpty: function (str: string): boolean {
        return str == 'undefined' || !!!str || !/[^\s]/.test(str);
    },
    removeSpace: function (str: string): string {
        if (this.isEmpty(str)) {
            return '';
        }
        return str.replace(/[\s]/g, '');
    },
    toStringArr(str: string): string[] {
        let arrResult: string[] = []
        if (this.isEmpty(str)) {
            return arrResult;
        }
        for (let i = 0;i < str.length; i++) {
            arrResult.push(str.charAt(i))
        }
        return arrResult
    },
    uint8ArrayToShowStr(uint8Array) {
        return Array.prototype.map
            .call(uint8Array, (x) => ('00' + x.toString(16)).slice(-2))
            .join('');
    },
    strArrToString(strArr: String[], offset: number, count: number) {
        if (!!!strArr || strArr.length == 0) {
            return ''
        }
        let resultStr: string = ''
        let maxStrCount = strArr.length - offset
        count = count > maxStrCount ? maxStrCount : count
        for (let i = offset;i < strArr.length; i++) {
            resultStr += strArr[i]
        }
        return resultStr
    },
    stringToUint8Array(str) {
        var arr = [];
        for (var i = 0, j = str.length; i < j; i++) {
            arr.push(str.charCodeAt(i));
        }
        return new Uint8Array(arr);
    },
    getCurrentDayTime() {
        let strResult: string = '';
        let dateNow: Date = new Date();
        strResult += (dateNow.getFullYear() % 1000 + '').padStart(2, '0')
        strResult += (dateNow.getMonth() + 1 + '').padStart(2, '0')
        strResult += (dateNow.getDate() + '').padStart(2, '0')
        strResult += (dateNow.getHours() + '').padStart(2, '0')
        strResult += (dateNow.getMinutes() + '').padStart(2, '0')
        strResult += (dateNow.getSeconds() + '').padStart(2, '0')
        return parseInt(strResult)
    },
    getNumberInStr(str: string) {
        if (this.isEmpty(str)) {
            return 0;
        }
        return parseInt(str)
    }
}